Passed
Push — master ( fc2d57...76a657 )
by Barry
01:39
created

findCode.js ➔ ... ➔ _findOpeningCodeFence   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 29
rs 8.8571
1
/** findMdpInsert and findCode functions use a similar layout to return the location and contents
2
  *   .start          => points at the character in the string why the other item starts (ie. comment or code block)
3
  *   .length         => is the overall length of the comment or code block.
4
  *   .internalStart  => points at the character in the string where the internal payload starts
5
  *   .internalLength => is the length of the internal payload
6
  *   .commandString  => is the command string found within the particular item
7
  *   .info           => is a structure containing further info about what was found
8
  * if start is returned as -1 then nothing was found
9
  *
10
  * The internalStart/internaLength defines the internal content which will be replaced. This does not include
11
  * leading and lagging CRLF/LF. So the replacement text is not required to have either leading or lagging line
12
  * endings. However, if the internalLength is negative this means that leading CRLF or LF must be added by the insertion
13
  * routine. The reason for this is that it allows insertions between code fences or mdpInsert pairs which have zero lines
14
  * between them.
15
  *
16
**/
17
18
const {earlierOf} = require('./helpers.js')
19
20
export function findCode (txt, start) {
21
  /**
22
  * finds the next code in the string provided starting at position start
23
  * returns an object containing start, length, internalStart, internalLength
24
  *
25
  * there are three types of code insertion - code span (inline), fenced code and indented code
26
  *
27
  * eg. span (1 or more backticks):
28
  * some text ``echo myfile.txt`` more text
29
  *
30
  * eg. indented (4 or more indent spaces):
31
  * some text
32
  *     function test() {
33
  *       console.log('test')
34
  *     }
35
  * more text
36
  *
37
  * eg. fenced (3 or more backticks on a row on their own)
38
  * some text
39
  * ``` js
40
  * function test() {
41
  *   console.log('test')
42
  * }
43
  * ```
44
  * more text
45
  *
46
  **/
47
  let x = _findFencedCode(txt, start)
48
  let y = _findIndentedCode(txt, start)
49
  let z = _findCodeSpan(txt, start)
50
51
  return earlierOf(x, earlierOf(y, z))
52
}
53
54
function _findFencedCode (txt, start) {
55
  // if the internalLength is returned as -1 this means that text cannot simply be inserted at the internalStart
56
  // location. Instead an additional preceding new line must be inserted along with the new text
57
  // another way to look at this is that the internal text is 1 character short
58
  // a value of -2 indicates a CRLF needs to be inserted
59
  let a = _findOpeningCodeFence(txt, start)
60
  if (a.start === -1) { return a }
61
  return _findClosingCodeFence(txt, a)
62
63
  function _findOpeningCodeFence (txt, start) {
64
    // returns the location and type of the next opening code fence
65
    let regex = /(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})(([ ]{0,3})([`]{3,}|[~]{3,})([^\n\r\0`]*))($|\r\n|\n)/g
66
    /** The regex groups are:
67
      * 0: the full match including any preamble block markup
68
      * 1: the leading new line character(s)
69
      * 2: the preamble consisting of block characters or nothing
70
      * 3: the full codeFence line without preamble
71
      * 4: any leading blank spaces at the start of the codeFence line
72
      * 5: the ` or ~ characters identifying the codeFence
73
      * 6: anything else on the line following the codeFence
74
      * 7: the final new line character(s)
75
    **/
76
    regex.lastIndex = start
77
    let regexResult = regex.exec(txt)
78
    if (regexResult === null) {
79
      return {start: -1}
80
    }
81
    let r = { start: regexResult.index + regexResult[1].length,
82
      info: {
83
        blockQuote: regexResult[2],
84
        spacesCount: regexResult[4].length,
85
        codeFence: regexResult[5]
86
      },
87
      commandString: regexResult[6].trim(),
88
      internalStart: regexResult.index + regexResult[0].length
89
    }
90
    return r
91
  }
92
93
  function _findClosingCodeFence (txt, opening) {
94
    // updates the passed result structure with the location and type of the next closing code fence
95
    // to match the opening cofeFence passed in
96
    let regex
97
    let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in
98
    regex = RegExp('(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})[ ]{0,3}[' + r.info.codeFence[0] + ']{' + r.info.codeFence.length + ',}[ ]*($|\r\n|\n)', 'g')
99
    regex.lastIndex = r.internalStart - 2
100
    let regexResult = regex.exec(txt)
101
    if (opening.info.blockQuote.length !== 0) {
102
      // we are in a block quote so the codeFence will end at the earlier of the found regex OR end of the block quote
103
      let b = _findEndOfBlock(txt, r.internalStart)
104
      if (b !== -1 && (regexResult === null || b < (regexResult.index + regexResult[1].length))) {
105
        // the block end dictates the code block end
106
        r.internalLength = b - r.internalStart
107
        r.length = b - r.start
108
        return r
109
      }
110
    }
111
    if (regexResult === null) {
112
      r.internalLength = txt.length - r.internalStart
113
      r.length = txt.length - r.start
114
    } else {
115
      r.internalLength = regexResult.index - r.internalStart
116
      r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start
117
    }
118
    return r
119
  }
120
121
  function _findEndOfBlock (txt, start) {
122
    // finds the first line which is not marked as block
123
    let regex = /(\r\n|\n)(?!([ ]{0,3}> |>))[^>\r\n]*/g
124
    regex.lastIndex = start
125
    let regexResult = regex.exec(txt)
126
    if (regexResult === null) {
127
      return -1
128
    } else {
129
      return regexResult.index
130
    }
131
  }
132
}
133
134
function _findIndentedCode (txt, start) {
135
  let regex = /((?:^|\r\n|\n)[ ]{4,}[^\r\n\0]*){1,}/g
136
  regex.lastIndex = start
137
  let regexResult = regex.exec(txt)
138
  if (regexResult === null) {
139
    return {start: -1}
140
  } else {
141
    return {
142
      start: regexResult.index,
143
      length: regexResult[0].length,
144
      internalStart: regexResult.index,
145
      internalLength: regexResult[0].length,
146
      info: {indent: regexResult[2]},
147
      commandString: ''
148
    }
149
  }
150
}
151
152
function _findCodeSpan (txt, start) {
153
  // finds an inline Code Span in the format: 'some text ``echo myfile.txt`` more text'
154
  // look for start
155
  let lookFrom = start
156
  while (true) {
157
    let s = _findCodeSpanStart(txt, lookFrom)
158
    if (s.start === -1) { return s }
159
    // look for end
160
    let e = _findCodeSpanEnd(txt, s)
161
    if (e.start !== -1) { return e }
162
    lookFrom = s.internalStart
163
  }
164
165
  function _findCodeSpanStart (txt, start) {
166
    let regex = /(^|[^`])(`+)[^`]/g
167
    // 1st capture group is the first (or no) character prior to the identifying `'s
168
    // 2nd group is the ` characters (however many there are)
169
    regex.lastIndex = start
170
    let regexResult = regex.exec(txt)
171
    if (regexResult === null) { return {start: -1} }
172
    let r = {
173
      start: regexResult.index + regexResult[1].length,
174
      internalStart: regexResult.index + regexResult[1].length + regexResult[2].length,
175
      info: {
176
        codeFence: regexResult[2]
177
      }
178
    }
179
    return r
180
  }
181
182
  function _findCodeSpanEnd (txt, opening) {
183
    let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in
184
    let regex = RegExp('([^`])(' + r.info.codeFence + ')($|[^`])', 'g')
185
    regex.lastIndex = r.internalStart
186
    let regexResult = regex.exec(txt)
187
    if (regexResult === null) { return {start: -1} }
188
    r.internalLength = regexResult.index + regexResult[1].length - r.internalStart
189
    r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start
190
    r.commandString = ''
191
    return r
192
  }
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
193
}
194